/**
 * weapp-js v1.7.1(2021-4-16)
 * https://codeup.aliyun.com/motigo/motigo/weapp_base
 * Copyright@ 2021 Motigo, Inc.
 * 
 */
/*
    录音接口封状模块

    basicRecorderManager
        特性：
            每次录音只有两个结果：
                成功和失败，被打断了也算是失败。
        备注：
            1、basicRecorderManager 不提供暂停和继续的接口，如果你需要，请你自己另外实现一个class，不要修改basicRecorderManager 去兼容各种场景
            2、basicRecorderManager 接管了微信原生RecorderManager的下列接口：
                - onInterruptionBegin
                - onInterruptionEnd
                - onPause
                - onResume
                - onError
                目前所有使用经验显示，onResume从未触发过，这其中任何一个被触发都将会让调用方收到录音失败的通知，如果多个同时触发，仅仅向调用方发送一次失败通知
*/

import { basicLock } from "../lock.js";
import { recordAuthorizeManager } from "./authorize.js";
import logging from "../../log.js";

export class basicRecorderManager extends recordAuthorizeManager{
    /*
        基础录音管理器

        每次录音只有两个结果：成功和失败

        录音被打断说明：
            1、被微信语音打断的时候：
                1.1 如果微信语音界面占满了全屏：
                    Event Stack:
                        onInterruptionBegin -> onPause -> onInterruptionEnd
                        onInterruptionBegin -> onPause
                        有的时候 onInterruptionEnd 不会触发，原因暂时不明
                    Return:返回微信
                    Note:
                        在onInterruptionBegin后调用stop或者在onPause后调用stop，会导致未来一段事件内start和stop没有问题，但是onStop中的参数 fileSize 和 duration 都为0，几分钟以后才恢复正常
                1.2 如果没有占满全屏，只是在小程序顶部显示一个微信语音的提示：
                    1.2.1 如果不点开顶部的微信语音提示：
                        Event Stack:
                            onInterruptionBegin
                        只会触发这一个回调，不会触发onPause,录音也不会终止。
                        如果没有设置duration参数，60s以后自动停止录音，下次再次录音start和stop可以正常调用，只是onStop的参数 fileSize 和 duration 都为0，几分钟以后才恢复正常
                    1.2.2 如果点开了顶部的微信语音提示：
                        将会跟1.1情况一样

                Note:微信语音界面占满了全屏多发生在小程序onLanch以后前几次微信语音/视频，如果重复拒绝多次，后面再来的就只是在小程序顶部显示一个提示，不强制占满全屏

            2、录音被手机来电打断的时候：
                Event Stack:
                    onPause
                Return:返回小程序
            3、企业微信：
                不会打断，也不会占用屏幕
            4、手机QQ：
                Event Stack:
                    onPause
                Return:返回手机QQ

            如果onPause回调触发，录音最终的状态都是被暂停状态，不会自动调用resum恢复录音，必须手动调用resume或者stop，恢复录音或者直接终止录音，下一次start才可以正常开始录音过程

            从微信语音或者微信视频返回的时候，直接回到微信界面，不返回小程序
            从手机电话返回的时候，会返回小程序中

        TODO:
            因为不敢保证封装适应所有情况。所以需要在不经常出现的异常发生的时候获取足够丰富的日志信息，进行排查。
            日志信息需要包括：用户操作路径、basicRecorderManager内部的执行逻辑顺序、事件触发、状态变迁
            使用微信的实时日志，或者提供接口让调用方设置处理日志的接口。

    */
    constructor(){
        super()
        this.rcd = wx.getRecorderManager();
        this.startLock = new basicLock()
        this.stopLock = new basicLock()
        Object.defineProperty(this,"ENUM",{
            configurable: false,
            writable: false,
            enumerable: true,
            value:Object.freeze({
                "NOTSTART":"NOTSTART",
                "WAIT":"WAIT",
                "RECORDING":"RECORDING",
                "SUCCESS":"SUCCESS",
                "FAILURE":"FAILURE",
            })
        })

        this.status = this.ENUM.NOTSTART;
    }

    start(options){
        /*
            开始录音

            参数说明：
                options:
                {
                    "sample":16000,     // 采样率
                    "channel":1,        // 通道
                    "bitrate":24000,    // 码率
                    "format":"mp3",     // 文件格式
                    "duration":60000,   // 录音时长
                    "onStart":()=>{},   // 录音正式开始回调
                    "success":(res)=>{  // 成功录音回调
                        // res.duration     录音时长，以毫秒为单位
                        // res.fileSize     文件大小，以字节为单位
                        // res.tempFilePath 录音文件本地路劲，可作为wx.uploadFile接口filePath参数，或者音频播放src参数
                    },
                    "fail":(res)=>{     // 录音失败回调
                        // res.source 失败产生的来源
                        // res.detail 失败产生的具体信息
                    },
                    "complete":(res)=>{ // 录音结束回调
                        // res.result 调用结果 成功success|失败fail
                        // res.data 成功的数据或者失败的数据
                    },
                }

            返回值说明：
                0：成功调用
                -1：调用失败，正在录音中
                -2：调用失败，加锁失败
        */


        if(this.status == this.ENUM.RECORDING){
            // 上一次录音没有结束，不可以开始新的录音
            logging.warn("user start failed. invalid state:",this.status)
            return -1;
        }

        if(this.startLock.lock() == false){
            // 防止短时间内重复调用
            logging.warn("user start failed. lock failed.")
            return -2;
        }

        this.status = this.ENUM.WAIT; // 等待录音onStart接口回调

        let completeLock = new basicLock()
        let stopOnPauseLock = new basicLock()

        const sample = options.sample;
        const channel = options.channel;
        const bitrate = options.bitrate;
        const format = options.format;
        const duration = options.duration;

        const rcd = this.rcd

        rcd.start({
            sampleRate:sample || 8000,
            numberOfChannels:channel || 2,
            encodeBitRate:bitrate || 48000,
            format:format || "aac",
            duration:duration || 60000,
        })

        let onComplete = (result,data)=>{
            options && options.complete && options.complete({
                "result":result,
                "data":data
            })
            this.startLock.unlock()
            this.stopLock.unlock()
        }

        let onFailure = (source,res)=>{
            logging.error("record failed. source : ",source)
            logging.error(res)
            let ret = completeLock.lock()
            if(ret == true){
                let result = {
                    "source":source,
                    "detail":res
                }
                options && options.fail && options.fail(result)
                onComplete("fail",result)
                this.status = this.ENUM.FAILURE
            }
            logging.info("lock ret:",ret)
        }

        let onSuccess = (res) =>{
            logging.info("record success.")
            logging.info(res)
            let ret = completeLock.lock()
            if( ret == true){
                options && options.success && options.success(res)
                onComplete("success",res)
                this.status = this.ENUM.SUCCESS
            }
            logging.info("lock ret:",ret)
        }

        let stopWhenPause = ()=>{
            // 防止 onInterruptionBegin 和 onPause 重复stop
            // 有的情况下只调用 onInterruptionBegin
            // 有的情况下只调用 onPause
            // 有的情况下 onInterruptionBegin 和 onPause 都会调用
            if(stopOnPauseLock.lock() == true){
                /*
                    录音被中断暂停以后，必须调用resume或者stop，否则start接口再也无法拉起录音
                */
                rcd.stop()
            }
        }

        rcd.onStart( res =>{
            options && options.onStart && options.onStart(res)

            // 录音开始，修改状态为正在录音中
            this.status = this.ENUM.RECORDING

            if(this.stopLock.isLocked() == true){
                // 用户在onStart前调用了stop
                rcd.stop()
            }
        })

        rcd.onError( res => {
            onFailure("onError",res)
        })

        rcd.onInterruptionBegin( res => {
            onFailure("onInterruptionBegin",res)
            /*
                录音被中断暂停以后，必须调用resume或者stop，否则start接口再也无法拉起录音
            */
            stopWhenPause()
        })

        rcd.onInterruptionEnd( res => {
            onFailure("onInterruptionEnd",res)
        })

        rcd.onPause( res => {
            onFailure("onPause",res)
            /*
                录音被中断暂停以后，必须调用resume或者stop，否则start接口再也无法拉起录音
            */
            stopWhenPause()
        })

        rcd.onResume( res => {
            onFailure("onResume",res)
        })

        rcd.onStop( res => {

            if(!( res && typeof res == "object"
                  && typeof res.tempFilePath == "string" && res.tempFilePath.length > 0
                  && typeof res.duration == "number" && res.duration > 0
                  && typeof res.fileSize == "number" && res.fileSize > 0)){
                // 录音文件有问题
                onFailure("onFileError",res)
            }

            else{
                onSuccess(res)
            }

        })

        return 0;
    }

    stop(){
        /*
            停止录音

            参数：无

            返回值：
                0：成功调用
                -1：调用失败，录音尚未开始
                -2：调用失败，加锁失败
                -3：调用失败，录音onStart尚未触发，将会在onStart触发后立即停止
        */
        if(this.status !== this.ENUM.RECORDING && this.status !== this.ENUM.WAIT){
            /*
                只有两种状态下可以调用stop接口：
                RECORDING   ： 设备正在录音
                WAIT        ： 已经调用了微信的start接口，但是onStart回调还没有触发
            */
            logging.warn("user stop failed. invalid state :",this.status)
            return -1;
        }

        if(this.stopLock.lock() == false){
            // 防止短时间内重复调用
            logging.warn("user stop failed. lock failed.")
            return -2;
        }

        if(this.status == this.ENUM.WAIT){
            // 不在第一个if中直接返回，而是加锁以后才返回，是因为这个时候是不能调用微信的stop接口的，否则会触发onError,recording is not start的错误提示
            // 加锁只是为了告诉onStart回调，用户在准备录音的过程中调用了stop接口，由onStart回调去调用微信的stop接口
            logging.warn("user stop pre-success between start and onStart")
            return -3;
        }

        this.rcd.stop()
        return 0;
    }
}
